#include "swagme.h"
#include "config.h"
#include "Game.h"
#include "Display.h"
#include "Player.h"
#include "Scene.h"
#include "Swag.h"

#include <iostream>
using std::cout;
using std::cerr;
using std::endl;

// 3 minutes for each screen
#define TIME_LEFT (3*60)

Game::Game() : 
    mTimeLeftInSec(TIME_LEFT), 
    mStartTimeInMs(SDL_GetTicks()), 
    mpDisplay(new Display()),
    mpScene(new Scene("resources/graphics/background1.png", "resources/graphics/map2.png")),
    mpPlayer(NULL),
    mpSwagPenSprite(NULL),
    mpConfig(InitConfig("resources/wad.xml")),
    mpCurLevel(NULL)
{
  if(!mpDisplay) {
    cerr << "Error!  Could not initialize Display" << endl;
    exit(4);
  }
  cout << "Game and Display initialized." << endl;
  if(!mpScene) { 
    cerr << "Error!  Could not initialize scene" << endl;
    exit(5);
  }
  Init();
}

Game::~Game()
{
  if(mpConfig) {
    delete mpConfig;
    mpConfig = NULL;
  }

  // TO DO: properly clean up sprite objects

  if(mpSwagPenSprite) {
    delete mpSwagPenSprite;
    mpSwagPenSprite = NULL;
  }

  if(mpScene) {
    delete mpScene;
    mpScene = NULL;
  }

  if(mpDisplay) {
    delete mpDisplay;
    mpDisplay = NULL;
  }

  for (int swagIdx = 0; swagIdx < mSwaggage.size(); swagIdx++) {
    delete mSwaggage[swagIdx];
  }

  cout << "Game and Display destroyed." << endl;
}

// set up font resources
// find the first conference
// set up player position
// load in all sprites, send them off to characters, etc
void Game::Init()
{
  // set up font resources

  // get all levels (order matters in the XML doc)
  int numLevels = mpConfig->getElementsByTagName("conference", &mAllLevels);
  if(!numLevels) {
    cerr << "Error!  Could not find any <conference> elements in wad.xml" << endl;
    exit(2000);
  }

  // find the first conference
  mpCurLevel = mAllLevels[0];
  if(!mpCurLevel) {
    cerr << "Error!  No suitable levels found (check order attributes on <conference> elements)" << endl;
    exit(2001);
  }
  cout << "First level was '" << mpCurLevel->attr["name"] << "'" << endl;

  // set up player position
  XMLNode* ps = mpCurLevel->getElementByTagName("playerStart");
  if(!ps ) {
    cerr << "Error!  Could not find <playerStart> element in first <conference> element" << endl;
    exit(2002);
  }

  // initialize player
  // first, get sprite
  XMLNode* psprite = mpConfig->getElementById("player-sprite");
  if(!psprite) {
    cerr << "Error!  Could not find <sprite id='player-sprite' .../>" << endl;
    exit(2003);
  }
  // now initialize with sprite, x, y, facing
  mpPlayer = new Player(psprite->attr["src"].c_str(),
                  ATOI(ps->attr["x"]),
                  ATOI(ps->attr["y"]));
  mpPlayer->SetFacing( (sm_direction)ATOI(ps->attr["facing"]) );

  // add some new swaggage to the board
  Swag *s;
  s = new Swag();
  s->Init("resources/graphics/swag-pen.png",30,30);
  mSwaggage.push_back(s);
  s = new Swag();
  s->Init("resources/graphics/swag-pen.png",330,30);
  mSwaggage.push_back(s);
  s = new Swag();
  s->Init("resources/graphics/swag-pen.png",30,330);
  mSwaggage.push_back(s);
  s = new Swag();
  s->Init("resources/graphics/swag-pen.png",330,330);
  mSwaggage.push_back(s);

  InitScene();
}

void Game::InitScene()
{
  // uses scene member variable
  if (!mpScene->Background) mpScene->Background = IMG_Load(mpScene->BackgroundImageFile);
  if (!mpScene->Background) {
    printf("Unable to load background image: %s\n", SDL_GetError());
    exit(1);
  }
  if (!mpScene->Map) mpScene->Map = IMG_Load(mpScene->MapImageFile);
  if (!mpScene->Map) {
    printf("Unable to load map image: %s\n", SDL_GetError());
    exit(1);
  }

  if (!mpSwagPenSprite)
    mpSwagPenSprite = new Sprite("resources/graphics/swag-pen.png");
}

// primarily what this is does is update the time left
// and display to the status area
void Game::Update(Uint8* keystate)
{
  if(IsGameOver()) { return; }

  if(mTimeLeftInSec > 0) {
    int oldTime = mTimeLeftInSec;
    mTimeLeftInSec = TIME_LEFT - (SDL_GetTicks() - mStartTimeInMs)/1000;
    if(mTimeLeftInSec != oldTime) {
//    cout << "Time Left (s) = " << mTimeLeftInSec << " seconds." << endl;
    }
  }

  // keyboard controls:
  // directions:
  //  w,a,d,s or up, left, down, right arrows or num pad equiv
//  static sm_direction mpPlayer->Facing = sm_dir_none;
  // if we want the player to just keep moving until they change direction, then this can be set to true or removed
  // I kind of like that - it's more like pacman
  bool moveKeyPressed=false; 
  
  // save the previous position of the player
  mpPlayer->prevX = mpPlayer->posX;
  mpPlayer->prevY = mpPlayer->posY;

  if (keystate[SDLK_w] || keystate[SDLK_UP] || keystate[SDLK_KP8]) {
    mpPlayer->SetFacing(sm_dir_up);
    moveKeyPressed = true;
  } 
  if (keystate[SDLK_a] || keystate[SDLK_LEFT] || keystate[SDLK_KP4]) {
    mpPlayer->SetFacing(sm_dir_left);
    moveKeyPressed = true;
  } 
  if (keystate[SDLK_s] || keystate[SDLK_DOWN] || keystate[SDLK_KP2]) {
    mpPlayer->SetFacing(sm_dir_down);
    moveKeyPressed = true;
  } 
  if (keystate[SDLK_d] || keystate[SDLK_RIGHT] || keystate[SDLK_KP6]) {
    mpPlayer->SetFacing(sm_dir_right);
    moveKeyPressed = true;
  }

  if (mpPlayer->GetFacing() != sm_dir_none) {
//    cout << "try to move " << mpPlayer->Facing << endl;
  }

  if (moveKeyPressed) {

    if(mpPlayer->GetFacing() == sm_dir_up) {
      mpPlayer->posY -= mpPlayer->speed;
    }
    if(mpPlayer->GetFacing() == sm_dir_left) {
      mpPlayer->posX -= mpPlayer->speed;
    }
    if(mpPlayer->GetFacing() == sm_dir_down) {
      mpPlayer->posY += mpPlayer->speed;
    }
    if(mpPlayer->GetFacing() == sm_dir_right) {
      mpPlayer->posX += mpPlayer->speed;
    }
  }

  // check edges of map
  int correctedX, correctedY;
  if (boundsCheck(mpPlayer->posX, mpPlayer->posY, correctedX, correctedY)) {
    mpPlayer->posX = correctedX;
    mpPlayer->posY = correctedY;
  }

  // check for collision with swag
  bool repeat=false;
  do {
    for (vector<Swag *>::iterator swagIt = mSwaggage.begin(); swagIt != mSwaggage.end(); ++swagIt) {
      if (collisionTest(mpPlayer->posX, mpPlayer->posY, (*swagIt)->posX, (*swagIt)->posY)) {
        mpPlayer->Points += 10;
        delete (*swagIt);
        mSwaggage.erase(swagIt);
        repeat = true;
        break;
      }
    }
    repeat = false;
  } while (repeat);

  mpDisplay->LockScreen();

  // commit to screen
  DrawScene();
  // update sprites
  mpPlayer->Draw(mpPlayer->speed, mpDisplay->getScreen());

  // this doesn't do anything yet, but mSprites could contain animations in the scene and maybe NPCs
  for (int spriteIdx = 0; spriteIdx < mSprites.size(); spriteIdx++) {
    mSprites[spriteIdx].Blit(mpDisplay->getScreen(),mpPlayer->posX,mpPlayer->posY);
  }

  for (int swagIdx = 0; swagIdx < mSwaggage.size(); swagIdx++) {
    mSwaggage[swagIdx]->Draw(mpDisplay->getScreen());
  }
  // TODO: only update when i need to
  DrawStatus();

  // flip the screen now that all rendering is complete 
  mpDisplay->UnlockScreen();
  mpDisplay->Flip();
}

void Game::DrawScene()
{
  // blit in layers,
  //  1. background
  //  2. map and status graphics
  //  3. sprites

  // temporary background - two rectangles against black for now 
  SDL_Surface* scr = mpDisplay->getScreen();
  SDL_Rect rcMap = { MAP_POS_X, MAP_POS_Y, MAP_SIZE_X, MAP_SIZE_Y };
  SDL_Rect rcStatus = { STATUS_POS_X, STATUS_POS_Y, STATUS_SIZE_X, STATUS_SIZE_Y };
  SDL_FillRect(scr, NULL, SDL_MapRGB(scr->format, 0, 0, 0)); // black
  SDL_FillRect(scr, &rcStatus, SDL_MapRGB(scr->format, 192, 255, 192)); // lt green

  SDL_BlitSurface(mpScene->Map, NULL, mpDisplay->getScreen(), &mpScene->MapRect);
}

void Game::DrawStatus()
{
  if(!mpDisplay || !mpPlayer) { return; }

  char szTimeStr[30];
  int time_x = STATUS_POS_X + 20; int time_y = STATUS_POS_Y + 50;
  SDL_Color black = {0,0,0,255}; 
  SDL_Color time_color = {0,0,0,255}; // black by default
  if(mTimeLeftInSec > 0) {
    sprintf(szTimeStr, "Time Left: %d:%02d", mTimeLeftInSec/60, mTimeLeftInSec%60);
  }
  else {
    sprintf(szTimeStr, "Game over, chump!");
    time_color.r = 255; // red
  }

  char szPlayerStr[30];
  int player_x = STATUS_POS_X + 10; int player_y = STATUS_POS_Y + 120;
  sprintf(szPlayerStr, "Pos: (%d,%d)", mpPlayer->posX, mpPlayer->posY);

  char szPlayerScore[20];
  int player_points_y = player_y + 20;
  sprintf(szPlayerScore, "Points: %d", mpPlayer->Points);

  char szLocation[40];
  sprintf(szLocation, "(%s)", mpCurLevel->attr["location"].c_str());

  int cx = STATUS_POS_X + (STATUS_SIZE_X/2);

  // rendering calls go here
  mpDisplay->SetCurrentFont("resources/fonts/VeraBd.ttf", 14);
  mpDisplay->DrawText(mpCurLevel->attr["name"].c_str(), black, 
                      cx, STATUS_POS_Y + 5, sm_disp_centerh);
  mpDisplay->SetCurrentFont("resources/fonts/Vera.ttf", 12);
  mpDisplay->DrawText(szLocation, black, 
                      cx, STATUS_POS_Y + 25, sm_disp_centerh);

  mpDisplay->SetCurrentFont("resources/fonts/Vera.ttf", 14);
  mpDisplay->DrawText(szTimeStr, time_color, cx, time_y, sm_disp_centerh);
  mpDisplay->DrawText(szPlayerStr, time_color, player_x, player_y);
  mpDisplay->DrawText(szPlayerScore, time_color, player_x, player_points_y);
}

// collision detection and reaction for the bounds of the map
// performs a bounds check with dimensions of the map
// returns 0 if check passes, 1 if correctedX, correctedY
// have changed with new values for position
int Game::boundsCheck(int x, int y, int &fixedX, int &fixedY) {
  int result = 0;
  if (x > PLAYER_POS_MAX_X) {
    fixedX = PLAYER_POS_MAX_X;
    fixedY = y;
    result = -1;
  }
  else if (x < PLAYER_POS_MIN_X) {
    fixedX = PLAYER_POS_MIN_X;
    fixedY = y;
    result = -1;
  }
  if (y > PLAYER_POS_MAX_Y) {
    fixedX = x;
    fixedY = PLAYER_POS_MAX_Y;
    result = -1;
  }
  else if (y < PLAYER_POS_MIN_Y) {
    fixedX = x;
    fixedY = PLAYER_POS_MIN_Y;
    result = -1;
  }
  return result;
}

// tests a 20x20 box in the middle of a box with top left at x1, y1 agains a box with top left x2, y2
// based on SDL_CollideBoundingBox() but not rects
int Game::collisionTest(int x1, int y1, int x2, int y2) {
  int w = 20, h = 20;
  x1+=6; x2+=6;
  y1+=6; y2+=6;
  if(x1 + w < x2) return 0;
  if(x2 + w < x1) return 0;

  if(y1 + h < y2) return 0;
  if(y2 + h < y1) return 0;

  return 1;
}
